package org.bigdata.platform.csv;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import org.bigdata.platform.BaseIteratorFileObject;
import org.bigdata.platform.IteratorElement;
import org.bigdata.platform.util.ArrayIterator;
import org.bigdata.platform.util.FileUtil;
/**
 * This class is a basic implementation of the interface ICsvFileObject. This class
 * can write and read data to and from CSV files. It can read data from file system
 * of any input stream. It handles custom separators, escaping and custom escape
 * characters. 
 * @author mayuresh
 *
 */
public class BaseCsvFileObject extends BaseIteratorFileObject<CsvRow> implements ICsvFileObject<CsvRow> {
//	/**
//	 * Head element of the CSV. It will be either first row of the CSV if
//	 * ignoreHeaders flag is true or second row of the CSV if ignoreHeaders
//	 * flag is false
//	 */
//	private CsvElement head;
//	/**
//	 * Current element in the CSV while looping through the CSV data.
//	 */
//	private CsvElement current;
//	/**
//	 * Last element of the CSV file
//	 */
//	private CsvElement tail;
	/**
	 * @see ignoreHeaders
	 */
	private boolean ignoreHeaders;
	/**
	 * @see getSeparator() and setSeparator()
	 */
	private String separator;
	/**
	 * @see setEscapeChar() and getEscapeChar()
	 */
	private char escapeChar = DEFAULT_ESCAPE_CHAR;
	/**
	 * Columns of the CSV file
	 */
	private List<CsvColumn> columns;
	/**
	 * Default constructor
	 */
	public BaseCsvFileObject() {
		this(true);
	}
	/**
	 * 
	 * @param ignoreHeaders
	 */
	public BaseCsvFileObject(boolean ignoreHeaders) {
		this(ignoreHeaders,DEFAULT_SEPARATOR);
	}
	/**
	 * 
	 * @param ignoreHeaders
	 * @param separator
	 */
	public BaseCsvFileObject(boolean ignoreHeaders, String separator) {
		this.ignoreHeaders = ignoreHeaders;
		this.separator = separator;
	}
	
	@Override
	public void readFrom(File file) throws Exception {
		readFrom(new FileInputStream(file));
	}

	@Override
	public void readFrom(InputStream is) throws Exception {
		List<String> lines = FileUtil.readLinesFromTextFile(is);
		int size = lines.size();
		ArrayList<CsvColumn> cols = new ArrayList<CsvColumn>();
		for (int i = 0; i < size; i++) {
			String line = lines.get(i);
			/* 
			 * Irrespective of whether we ignore the headers or not, we have to find 
			 * out number of columns. We do this at the time of parsing the first line
			 * The first line tells us how many columns we can except from the whole
			 * file. We throw exception if we find less or more number of columns in
			 * further data.
			 */
			String [] dataCols = parseLine(line);
			IteratorElement<CsvRow> e = new IteratorElement<CsvRow>();
			e.setData(new CsvRow(dataCols));
			if(i == 0) {
				for (int j = 0; j < dataCols.length; j++) {
					CsvColumn col = new CsvColumn();
					if(!ignoreHeaders) {
						col.setLabel(dataCols[j]);
					}
					cols.add(col);
				}
				this.columns = cols;
				if(ignoreHeaders) {
					addElement(e);
				}
			} else {
				addElement(e);
			}
			
		}
	}
	
	/**
	 * Parses the line after applying all the escaping rules and returns
	 * tokens in the form of String array.
	 * @param line - Line from CSV file
	 * @return - Tokens
	 */
	private String [] parseLine(String line) {
		String [] dataCols = line.split(getSeparatorRegEx());
		ArrayIterator<String> iter = new ArrayIterator<String>(dataCols);
		ArrayList<String> res = new ArrayList<String>();
		while(iter.hasNext()) {
			String next = iter.next();
			while(next.endsWith(""+getEscapeChar())) {
				next = next.substring(0, (next.length()-1));
				next += getSeparator();
				if(iter.hasNext()) {
					next += iter.next();
				}
			}
			res.add(next);
		}
		return res.toArray(new String [res.size()]);
	}
	/**
	 * Creates and returns regular expression out from separator string.
	 * @return - Regex string from separator
	 */
	public String getSeparatorRegEx() {
		int len = this.separator.length();
		StringBuilder res = new StringBuilder();
		for (int i = 0; i < len; i++) res.append("[").append(this.separator.charAt(i)).append("]");
		return res.toString();
	}
	@Override
	public void writeTo(File file) throws Exception {
		writeTo(new FileOutputStream(file));
	}
	@Override
	public void writeTo(OutputStream os) {
		if(this.columns == null || this.columns.isEmpty()) {
			throw new RuntimeException("Column list is empty");
		}
		PrintWriter pw = new PrintWriter(os);
		int length = getColumnCount();
		if(!ignoreHeaders) {
			// Print Headers
			for (int i = 1; i <= length; i++) {
				String label = getColumn(i).getLabel();
				label = label == null ? "col"+i : label;
				String labelString = label.replaceAll("["+getSeparator()+"]", getEscapeString()+getSeparator());
				pw.print(labelString);
				if(i < length) pw.print(getSeparator());
			}
			pw.println();
		}
		// Print rows
		while(hasNext()) {
			IteratorElement<CsvRow> row = next();
			for (int i = 1; i <= length; i++) {
				Object data = row.getData().getData(i);
				String dataString = data == null ? "" : data.toString().replaceAll("["+getSeparator()+"]", getEscapeString()+getSeparator());
				pw.print(dataString);
				if(i < length) pw.print(getSeparator());
			}
			pw.println();
		}
		pw.flush();
		pw.close();
	}

	@Override
	public int getColumnCount() {
		return this.columns == null ? 0 : this.columns.size();
	}

	@Override
	public CsvColumn getColumn(int indexStartwWith1) {
		if(indexStartwWith1 > getColumnCount()) {
			throw new ArrayIndexOutOfBoundsException("Index is greater than column count.");
		}
		return this.columns.get(indexStartwWith1-1);
	}

	@Override
	public String getSeparator() {
		return this.separator;
	}

	@Override
	public void setColumns(List<CsvColumn> columns) {
		this.columns = columns;
	}

	@Override
	public void setSeparator(String separator) {
		this.separator = separator;
	}

	@Override
	public boolean ignoreHeaders() {
		return this.ignoreHeaders;
	}

	@Override
	public void setIgnoreHeaders(boolean ignoreHeaders) {
		this.ignoreHeaders = ignoreHeaders;
	}


	@Override
	public char getEscapeChar() {
		return this.escapeChar;
	}

	@Override
	public void setEscapeChar(char escapeChar) {
		this.escapeChar = escapeChar;
	}
	private String getEscapeString() {
		return "\\" + this.escapeChar + "\\";
	}
	@Override
	public void remove() {
		throw new RuntimeException("Method not supported : remove()");
	}
}
